/*
 * Copyright (C) 2018 Ortega Froysa, Nicolás <nortega@themusicinnoise.net>
 * Author: Ortega Froysa, Nicolás <nortega@themusicinnoise.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define WINDOW_WIDTH 1200
#define WINDOW_HEIGHT 700
#define FPS 60

#include "System.hpp"
#include <iostream>

std::unique_ptr<Logger> System::logger;

System::System() : running(true) {
	logger = std::make_unique<Logger>("vectorwars.log");
	logger->write("Initializing systems.");
	if(SDL_Init(/*SDL_INIT_TIMER bitor*/ SDL_INIT_VIDEO bitor
				SDL_INIT_AUDIO bitor SDL_INIT_EVENTS))
	{
		logger->writeError("Failed to initialize SDL2.");
		exit(1);
	}

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
			SDL_GL_CONTEXT_PROFILE_CORE);
	window = SDL_CreateWindow("TTT3D",
				SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
				WINDOW_WIDTH, WINDOW_HEIGHT,
				SDL_WINDOW_OPENGL bitor SDL_WINDOW_SHOWN);
	if(not window)
	{
		logger->writeError("Failed to create window.");
		exit(1);
	}

	glcontext = SDL_GL_CreateContext(window);

	SDL_GL_SetSwapInterval(0);
	glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
	glewExperimental = 0;
	if(glewInit() not_eq GLEW_OK)
	{
		logger->writeError("GLEW failed to initialize.");
		exit(1);
	}

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);
	glEnable(GL_CULL_FACE);

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

	shaders.insert({"default", Shader(
				"../assets/shaders/vertex_shader.glsl",
				"../assets/shaders/fragment_shader.glsl")});
	asset_mngr.loadModel(
			"../assets/models/board.obj",
			"board");
	asset_mngr.loadModel(
			"../assets/models/cube.obj",
			"cube");
	asset_mngr.loadModel(
			"../assets/models/sphere.obj",
			"sphere");
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++){
			board[i][j]=0;
		}
	}
	xturn=true;
	tempx=1;
	tempy=1;
}
int System::CheckWin(){
	for(int i=0;i<3;i++){
		if(board[0][i]==board[1][i] && board[0][i]==board[2][i]){
			if(board[0][i]!=0){
				return board[0][i];
			}
		}
	}
	for(int i=0;i<3;i++){
		if(board[i][0]==board[i][1] && board[i][0]==board[i][2]){
			if(board[i][0]!=0){
				return board[i][0];
			}
		}
	}
	if(board[1][1]!=0){
		if(board[0][0]==board[1][1] && board[1][1]==board[2][2]){
			return board[1][1];
		}
		if(board[0][2]==board[1][1] && board[1][1]==board[2][0]){
			return board[1][1];
		}
	}
	return 0;
}

System::~System() {
	logger->write("Shutting down systems.");
	SDL_GL_DeleteContext(glcontext);
	SDL_DestroyWindow(window);
	SDL_Quit();
}

void System::run() {
	unsigned int lastTime = SDL_GetTicks();
	cam.setPosition(glm::vec3(8,8,8));
	while(running)
	{
		syncInputs();

		glClear(GL_COLOR_BUFFER_BIT bitor GL_DEPTH_BUFFER_BIT);
		shaders.at("default").use();
		shaders.at("default").setVec3("light_col",
				glm::vec3(1.0f, 1.0f, 0.8f));
		shaders.at("default").setVec3("light_dir",
				glm::vec3(-0.3f, -1, 0));
		{
			glm::mat4 proj = glm::perspective(glm::radians(45.0f),
					static_cast<float>(WINDOW_WIDTH) / static_cast<float>(WINDOW_HEIGHT),
					0.1f, 100.0f);

			glm::mat4 view = glm::lookAt(
					cam.get_pos(), // camera position
					glm::vec3(0,0,0), // where the camera is looking
					glm::vec3(0,1,0) // which way is vertically up
					);
			shaders.at("default").setMat4("proj", proj);
			shaders.at("default").setMat4("view", view);
		}

		{
			// board model
			glm::mat4 model = glm::mat4(1.0f);
			shaders.at("default").setMat4("model", model);
		}
		asset_mngr.getModel("board")->setColor(0.87f,0.721f,0.529f);
		asset_mngr.getModel("board")->draw(shaders.at("default"));

		{
			// temprary piece model
			glm::mat4 model = glm::mat4(1.0f);
			model = glm::translate(model,
					glm::vec3((tempx-1) * 3, 2.5f, (tempy-1) * 3));
			shaders.at("default").setMat4("model", model);
		}
		if(xturn)
		{
			if(board[tempx][tempy] not_eq 0)
				asset_mngr.getModel("cube")->setColor(1,0,0);
			else
				asset_mngr.getModel("cube")->setColor(0,1,0);

			asset_mngr.getModel("cube")->draw(shaders.at("default"));
		}
		else
		{
			if(board[tempx][tempy] not_eq 0)
				asset_mngr.getModel("sphere")->setColor(1,0,0);
			else
				asset_mngr.getModel("sphere")->setColor(0,1,0);

			asset_mngr.getModel("sphere")->draw(shaders.at("default"));
		}

		for(int i = 0; i < 3; ++i)
		{
			for(int j = 0; j < 3; ++j)
			{
				if(board[i][j] not_eq 0)
				{
					glm::mat4 model = glm::mat4(1.0f);
					model = glm::translate(model,
							glm::vec3((i-1) * 3, 1, (j-1) * 3));
					shaders.at("default").setMat4("model", model);
					if(board[i][j] == 1)
					{
						asset_mngr.getModel("cube")->setColor(0,0,1);
						asset_mngr.getModel("cube")->draw(shaders.at("default"));
					}
					else if(board[i][j] == 2)
					{
						asset_mngr.getModel("sphere")->setColor(0,0,1);
						asset_mngr.getModel("sphere")->draw(shaders.at("default"));
					}
				}
			}
		}

		SDL_GL_SwapWindow(window);

		if(SDL_GetTicks() - lastTime < 1000 / FPS)
		{
			SDL_Delay((1000 / FPS) -
					(SDL_GetTicks() - lastTime));
		}
		lastTime = SDL_GetTicks();
	}
}

void System::handleKey(SDL_Keysym key){
	switch(key.sym)
	{
		case SDLK_UP:
			if(tempy > 0)
				tempy--;
			break;
		case SDLK_DOWN:
			if(tempy < 2)
				tempy++;
			break;
		case SDLK_RIGHT:
			if(tempx < 2)
				tempx++;
			break;
		case SDLK_LEFT:
			if(tempx > 0)
				tempx--;
			break;
		case SDLK_SPACE:
			if(board[tempx][tempy]!=0){
				break;
			}
			board[tempx][tempy]=xturn?1:2;
			tempx=1;
			tempy=1;
			xturn=!xturn;
			int winner=CheckWin();
			if(winner==1){
				logger->write("Player 1 wins!");
				running=false;
			}
			if(winner==2){
				logger->write("Player 2 wins");
				running=false;
			}
			break;
	}
}

void System::syncInputs() {
	SDL_Event e;
	while(SDL_PollEvent(&e))
	{
		switch(e.type)
		{
			case SDL_QUIT:
				running = false;
				break;
			case SDL_KEYDOWN:
				handleKey(e.key.keysym);
				break;
		}
	}
}
